﻿using System;
using System.Collections.Generic;
using System.Linq;
using Myotragus.Data.Criteria;
using Myotragus.Data.Domain;
using Myotragus.Data.Util;
using NHibernate;

namespace Myotragus.Data.RepositoryExtensions
{
  public static class LocalizableWriteRepositoryExtensions
  {
    public static ILocalizableWriteRepository<TId, TEntity, TDescriptor, TProjector, TAlias>
      Save<TId, TEntity, TDescriptor, TProjector, TAlias>
      (
        this ILocalizableWriteRepository<TId, TEntity, TDescriptor, TProjector, TAlias> @this,
        int lcId, params TEntity[] entities
      )
      where TEntity : class, IEntity<TId>
      where TDescriptor : ILocalizableEntityDescriptor<TEntity>
      where TProjector : IProjector
      where TAlias : ILocalizableAlias
    {
      return @this.Save(lcId, (IEnumerable<TEntity>)entities);
    }

    public static ILocalizableWriteRepository<TId, TEntity, TDescriptor, TProjector, TAlias> 
      Save<TId, TEntity, TDescriptor, TProjector, TAlias>
      (
        this ILocalizableWriteRepository<TId, TEntity, TDescriptor, TProjector, TAlias> @this,
        int lcId, IEnumerable<TEntity> entities
      )
      where TEntity : class, IEntity<TId>
      where TDescriptor : ILocalizableEntityDescriptor<TEntity> where TProjector : IProjector
      where TAlias : ILocalizableAlias
    {
      using (var session = @this.OpenSession())
      {
        foreach (var obj in entities)
        {
          var ops = CreateAndInitializeSaveEntity(@this, session, obj, lcId);

          using (var transaction = session.BeginTransaction())
          {
            session.SaveOrUpdate(ops);
            transaction.Commit();

            obj.Id = ops.Id;
          }
        }

        session.Flush();
      }

      return @this;
    }

    public static ILocalizableWriteRepository<TId, TEntity, TDescriptor, TProjector, TAlias> 
      Delete<TId, TEntity, TDescriptor, TProjector, TAlias>
      (
        this ILocalizableWriteRepository<TId, TEntity, TDescriptor, TProjector, TAlias> @this,
        IEnumerable<TEntity> entities
      )
      where TEntity : class, IEntity<TId>
      where TDescriptor : ILocalizableEntityDescriptor<TEntity> 
      where TProjector : IProjector
      where TAlias : ILocalizableAlias
    {
      var descriptor = LocalizableCriteriaHelper.ResolveEntityDescriptor(@this);
      using (var session = @this.OpenSession())
      {
        foreach (var ops in entities.Select(x => 
            GetSaveEntity<TId, TEntity, TDescriptor>(descriptor, session, x.Id)))
          using (var transaction = session.BeginTransaction())
          {
            session.Delete(ops);
            transaction.Commit();
          }

        session.Flush();
      }
      return @this;
    }
    
    public static ILocalizableWriteRepository<TId, TEntity, TDescriptor, TProjector, TAlias> 
      Delete<TId, TEntity, TDescriptor, TProjector, TAlias>
      (
        this ILocalizableWriteRepository<TId, TEntity, TDescriptor, TProjector, TAlias> @this,
        params TEntity[] entities
      )
      where TEntity : class, IEntity<TId>
      where TDescriptor : ILocalizableEntityDescriptor<TEntity> where TProjector : IProjector
      where TAlias : ILocalizableAlias
    {
      return @this.Delete((IEnumerable<TEntity>)entities);
    }

    private static IEntity<TId> 
      CreateAndInitializeSaveEntity<TId, TEntity, TDescriptor, TProjector, TAlias>
      (
        ILocalizableWriteRepository<TId, TEntity, TDescriptor, TProjector, TAlias> @this, 
        ISession session, TEntity obj, int lcId
      ) 
      where TEntity : IEntity<TId> 
      where TDescriptor : ILocalizableEntityDescriptor<TEntity> 
      where TProjector : IProjector 
      where TAlias : ILocalizableAlias
    {
      var descriptor = LocalizableCriteriaHelper.ResolveEntityDescriptor(@this);
      var ops = IsUpdate(obj.Id) 
                  ? GetSaveEntity<TId, TEntity, TDescriptor>(descriptor, session, obj.Id)
                  : CreateNewSaveEntity<TId, TEntity, TDescriptor>(descriptor, obj);
      
      foreach (var prop in descriptor.LocalizableProperties)
      {
        var arrEmpty = ReflectionHelper.EmptyObjects;
        var value = (string)descriptor.EntityProperties[prop].GetValue(obj, arrEmpty);
        var text = (ILocalizableText)descriptor.SaveEntityProperties[prop].GetValue(ops, arrEmpty);
        text.Values[lcId] = value;
      }

      return ops;
    }

    private static IEntity<TId> 
      CreateNewSaveEntity<TId, TEntity, TDescriptor>
      (
        TDescriptor descriptor, TEntity prototype
      )
      where TDescriptor : ILocalizableEntityDescriptor<TEntity>
      where TEntity : IEntity<TId>
    {
      var result = (IEntity<TId>)Activator.CreateInstance(descriptor.SaveEntityImplementation);

      ReflectionHelper.InitializeObject(result, prototype);

      foreach (var name in descriptor.LocalizableProperties)
      {
        var prop = descriptor.SaveEntityProperties[name];
        var text = Activator.CreateInstance(prop.PropertyType);
        prop.SetValue(result, text, ReflectionHelper.EmptyObjects);
      }

      return result;
    }

    private static IEntity<TId> 
      GetSaveEntity<TId, TEntity, TDescriptor>
      (
        TDescriptor descriptor, ISession session, TId id
      )
      where TDescriptor : ILocalizableEntityDescriptor<TEntity> 
      where TEntity : IEntity<TId>
    {
      // TODO: Include checking for wrong update values
      return (IEntity<TId>)session.Get(descriptor.SaveEntityImplementation, id);
    }

    private static bool IsUpdate<TId>(TId id)
    {
      // TODO: Better update or save discrimination needed
      return ! Equals(id, default(TId));
    }
  }
}